// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/renderer/p2p/socket_dispatcher.h"

#include "base/bind.h"
#include "base/memory/ref_counted.h"
#include "content/child/child_process.h"
#include "content/common/p2p_messages.h"
#include "content/renderer/p2p/host_address_request.h"
#include "content/renderer/p2p/network_list_observer.h"
#include "content/renderer/p2p/socket_client_impl.h"
#include "content/renderer/render_view_impl.h"
#include "ipc/ipc_sender.h"

namespace content {

P2PSocketDispatcher::P2PSocketDispatcher(
    base::SingleThreadTaskRunner* ipc_task_runner)
    : ipc_task_runner_(ipc_task_runner)
    , network_notifications_started_(false)
    , network_list_observers_(
          new base::ObserverListThreadSafe<NetworkListObserver>())
    , sender_(NULL)
{
}

P2PSocketDispatcher::~P2PSocketDispatcher()
{
    network_list_observers_->AssertEmpty();
    for (IDMap<P2PSocketClientImpl*>::iterator i(&clients_); !i.IsAtEnd();
         i.Advance()) {
        i.GetCurrentValue()->Detach();
    }
}

void P2PSocketDispatcher::AddNetworkListObserver(
    NetworkListObserver* network_list_observer)
{
    network_list_observers_->AddObserver(network_list_observer);
    network_notifications_started_ = true;
    SendP2PMessage(new P2PHostMsg_StartNetworkNotifications());
}

void P2PSocketDispatcher::RemoveNetworkListObserver(
    NetworkListObserver* network_list_observer)
{
    network_list_observers_->RemoveObserver(network_list_observer);
}

void P2PSocketDispatcher::Send(IPC::Message* message)
{
    DCHECK(ipc_task_runner_->BelongsToCurrentThread());
    if (!sender_) {
        DLOG(WARNING) << "P2PSocketDispatcher::Send() - Sender closed.";
        delete message;
        return;
    }

    sender_->Send(message);
}

bool P2PSocketDispatcher::OnMessageReceived(const IPC::Message& message)
{
    bool handled = true;
    IPC_BEGIN_MESSAGE_MAP(P2PSocketDispatcher, message)
    IPC_MESSAGE_HANDLER(P2PMsg_NetworkListChanged, OnNetworkListChanged)
    IPC_MESSAGE_HANDLER(P2PMsg_GetHostAddressResult, OnGetHostAddressResult)
    IPC_MESSAGE_HANDLER(P2PMsg_OnSocketCreated, OnSocketCreated)
    IPC_MESSAGE_HANDLER(P2PMsg_OnIncomingTcpConnection, OnIncomingTcpConnection)
    IPC_MESSAGE_HANDLER(P2PMsg_OnSendComplete, OnSendComplete)
    IPC_MESSAGE_HANDLER(P2PMsg_OnError, OnError)
    IPC_MESSAGE_HANDLER(P2PMsg_OnDataReceived, OnDataReceived)
    IPC_MESSAGE_UNHANDLED(handled = false)
    IPC_END_MESSAGE_MAP()
    return handled;
}

void P2PSocketDispatcher::OnFilterAdded(IPC::Channel* channel)
{
    DVLOG(1) << "P2PSocketDispatcher::OnFilterAdded()";
    sender_ = channel;
}

void P2PSocketDispatcher::OnFilterRemoved()
{
    sender_ = NULL;
}

void P2PSocketDispatcher::OnChannelConnected(int32_t peer_id)
{
    connected_ = true;
}

void P2PSocketDispatcher::OnChannelClosing()
{
    sender_ = NULL;
    connected_ = false;
}

base::SingleThreadTaskRunner* P2PSocketDispatcher::task_runner()
{
    return ipc_task_runner_.get();
}

int P2PSocketDispatcher::RegisterClient(P2PSocketClientImpl* client)
{
    DCHECK(ipc_task_runner_->BelongsToCurrentThread());
    return clients_.Add(client);
}

void P2PSocketDispatcher::UnregisterClient(int id)
{
    DCHECK(ipc_task_runner_->BelongsToCurrentThread());
    clients_.Remove(id);
}

void P2PSocketDispatcher::SendP2PMessage(IPC::Message* msg)
{
    if (!ipc_task_runner_->BelongsToCurrentThread()) {
        ipc_task_runner_->PostTask(
            FROM_HERE, base::Bind(&P2PSocketDispatcher::Send, this, msg));
        return;
    }
    Send(msg);
}

int P2PSocketDispatcher::RegisterHostAddressRequest(
    P2PAsyncAddressResolver* request)
{
    DCHECK(ipc_task_runner_->BelongsToCurrentThread());
    return host_address_requests_.Add(request);
}

void P2PSocketDispatcher::UnregisterHostAddressRequest(int id)
{
    DCHECK(ipc_task_runner_->BelongsToCurrentThread());
    host_address_requests_.Remove(id);
}

void P2PSocketDispatcher::OnNetworkListChanged(
    const net::NetworkInterfaceList& networks,
    const net::IPAddress& default_ipv4_local_address,
    const net::IPAddress& default_ipv6_local_address)
{
    network_list_observers_->Notify(
        FROM_HERE, &NetworkListObserver::OnNetworkListChanged, networks,
        default_ipv4_local_address, default_ipv6_local_address);
}

void P2PSocketDispatcher::OnGetHostAddressResult(
    int32_t request_id,
    const net::IPAddressList& addresses)
{
    P2PAsyncAddressResolver* request = host_address_requests_.Lookup(request_id);
    if (!request) {
        DVLOG(1) << "Received P2P message for socket that doesn't exist.";
        return;
    }

    request->OnResponse(addresses);
}

void P2PSocketDispatcher::OnSocketCreated(
    int socket_id,
    const net::IPEndPoint& local_address,
    const net::IPEndPoint& remote_address)
{
    P2PSocketClientImpl* client = GetClient(socket_id);
    if (client) {
        client->OnSocketCreated(local_address, remote_address);
    }
}

void P2PSocketDispatcher::OnIncomingTcpConnection(
    int socket_id, const net::IPEndPoint& address)
{
    P2PSocketClientImpl* client = GetClient(socket_id);
    if (client) {
        client->OnIncomingTcpConnection(address);
    }
}

void P2PSocketDispatcher::OnSendComplete(
    int socket_id,
    const P2PSendPacketMetrics& send_metrics)
{
    P2PSocketClientImpl* client = GetClient(socket_id);
    if (client) {
        client->OnSendComplete(send_metrics);
    }
}

void P2PSocketDispatcher::OnError(int socket_id)
{
    P2PSocketClientImpl* client = GetClient(socket_id);
    if (client) {
        client->OnError();
    }
}

void P2PSocketDispatcher::OnDataReceived(
    int socket_id, const net::IPEndPoint& address,
    const std::vector<char>& data,
    const base::TimeTicks& timestamp)
{
    P2PSocketClientImpl* client = GetClient(socket_id);
    if (client) {
        client->OnDataReceived(address, data, timestamp);
    }
}

P2PSocketClientImpl* P2PSocketDispatcher::GetClient(int socket_id)
{
    P2PSocketClientImpl* client = clients_.Lookup(socket_id);
    if (client == NULL) {
        // This may happen if the socket was closed, but the browser side
        // hasn't processed the close message by the time it sends the
        // message to the renderer.
        DVLOG(1) << "Received P2P message for socket that doesn't exist.";
        return NULL;
    }

    return client;
}

} // namespace content
